(function (exports) {
	if('undefined'!==typeof(require)) {
		var qubic = require('./qubic-grammar').parser;
		var sys = require('sys');
	} else {
		qubic = parser;
	}
	
	var unquote = function (s) {
		return String(s).replace(/^"|"$/g, '');
	}
	
	var QVM = function () {
		this.V = {};
		this.sysvars = {};
		this.internals = {};
		
		this.init();
		
		this.gotoLabel = null;
		this.labels2nums={};
		
	}
	
	QVM.prototype.init = function () {
		var _qvm = this.constructor;
		for(var i in _qvm.defaultInternals) {
			if(_qvm.defaultInternals.hasOwnProperty(i)) {
				this.internals[i] = _qvm.defaultInternals[i];
			}
		}
		
	};
	QVM.prototype.invoke = function (name, args, next) {
		var self = this;
		this.internals[name].call(this, args, function (result){ next.call(self, result); });
	};
	QVM.prototype.compile = function (code) {
		return qubic.parse(code);
	};
	QVM.prototype.cb = function (method) {
		var self = this,
			aargs = Array.prototype.slice.call(arguments);
		aargs.shift();
		setTimeout(function () {
			method.apply(self, aargs);
		}, 1);
	};
	QVM.prototype.run = function (node, next) {
		var self = this;
		var type = node[0], i = node[1];

		var opcode = {
			
			STLIST : function (args, next) {
				var stNum = 0, stLen = args.items.length;
				if (args.root) {
					for (var i = 0; i < args.items.length; i++) {
						if(undefined !== args.items[i][1].label) {
							self.labels2nums[args.items[i][1].label] = i;
						}
					}
				}
				
				function nextStatement () {
					if(stNum >= stLen || stNum < 0 ) {
						next.call(self);
						return;
					} else {
						self.run(args.items[stNum], function () {
							if (null!==self.gotoLabel) {
								if(args.root) {
									if(self.labels2nums[self.gotoLabel]!==undefined) {
										stNum = self.labels2nums[self.gotoLabel];
									} else {
										throw ('Cannot GOTO' + self.gotoLabel);
									}
									self.gotoLabel = null;
								} else {
									stNum = -1;
									nextStatement();
									return;
								}
							} else {
								stNum++;
							}
							nextStatement();
							return;
						});
					}
				}
				nextStatement();
			},
			
			ST_GOTO : function (args, next) {
				this.gotoLabel = String(args.dest);
				next.call(self);
			},
			
			ST_IF : function (args, next) {
				self.run(args.cond, function (result) {
					result = !!result;
					if(result) {
						self.run(args.then, function () {
							next.call(self);
						});
					} else if(undefined !== args['else']) {
						self.run(args['else'], function () {
							next.call(self);
						});
					} else {
						next.call(self);
					}
				});
			},
			
			ST_WHILE : function (args, next) {
				/*
				 * вычислить cond
				 * если cond, то {
					 * выполнить body, перейдя на начало
				 * } иначе { перейти дальше }
				 */
				function w () {
					self.run(args.cond, function (result) {
						if(!!result) {
							self.run(args.body, function() {
								w(args, next);
							});
						} else {
							next.call(self);
						}
					});
				}
				w();
			},
			
			ST_LET : function (args, next) {
				self.run(args.r, function (r) {
					self.setv(args.l[1].name, r);
					next.call(self);
				});
			},
			
			VAR : function (args, next) {
				next.call(self, self.getv(args.name));
			},
			
			CALL : function (args, next) {
				if(args.args) {
					self.run(args.args, function (argList) {
						self.invoke(args.func, argList, next);
					});
				} else {
					self.invoke(args.func, [], next);
				}
			},
			
			LIST : function (args, next) {
				var lsNum = 0, lsLen = args.items.length, values = [];
				function nextItem () {
					if(lsNum >= lsLen) {
						next.call(self, values);
					} else {
						self.run(args.items[lsNum], function (result) {
							values.push(result);
							lsNum++;
							nextItem();
						});
					}
				}
				nextItem();
			},
			
			INT : function (args, next) {
				next.call(self, parseInt(args.value));
			},
			
			STR : function (args, next) {
				next.call(self, unquote(args.value));
			},
			
			ST_EXP : function (args, next) {
				self.run(args.exp, next); 
			},
			
			MUL : function (args, next) {
				self.run(args.l, function (l) {
					self.run (args.r, function (r) {
						var o = l * r;
						next.call(self, o);
					})
				});
			},
			ADD : function (args, next) {
				self.run(args.l, function (l) {
					self.run (args.r, function (r) {
						var o = l + r;
						next.call(self, o);
					})
				});
			},
			CONCAT : function (args, next) {
				self.run(args.l, function (l) {
					self.run (args.r, function (r) {
						var o = String(l) + String(r);
						next.call(self, o);
					})
				});
			},
			SUB : function (args, next) {
				self.run(args.l, function (l) {
					self.run (args.r, function (r) {
						var o = l - r;
						next.call(self, o);
					})
				});
			},
			DIV : function (args, next) {
				self.run(args.l, function (l) {
					self.run (args.r, function (r) {
						var o = l / r;
						next.call(self, o);
					})
				});
			},
			EQ : function (args, next) {
				self.run(args.l, function (l) {
					self.run (args.r, function (r) {
						var o = (l == r);
						next.call(self, o);
					})
				});
			},
			GT : function (args, next) {
				self.run(args.l, function (l) {
					self.run (args.r, function (r) {
						var o = (l > r);
						next.call(self, o);
					})
				});
			},
			LT : function (args, next) {
				self.run(args.l, function (l) {
					self.run (args.r, function (r) {
						var o = (l < r);
						next.call(self, o);
					})
				});
			},
			GE : function (args, next) {
				self.run(args.l, function (l) {
					self.run (args.r, function (r) {
						var o = (l >= r);
						next.call(self, o);
					})
				});
			},
			LE : function (args, next) {
				self.run(args.l, function (l) {
					self.run (args.r, function (r) {
						var o = (l <= r);
						next.call(self, o);
					})
				});
			},
			NE : function (args, next) {
				self.run(args.l, function (l) {
					self.run (args.r, function (r) {
						var o = (l != r);
						next.call(self, o);
					})
				});
			},
			
		};
		
		self.cb(opcode[type], i, next);
		
	};
	QVM.prototype.getv = function (name) {
		return this.V[name];
	};
	QVM.prototype.setv = function (name, value) {
		this.V[name] = value;
	};
	



	QVM.defaultInternals = {
		PRINT : function (args, next) {
			for(var i = 0; i < args.length; i++) {
				sys.puts(args[i]);
			}
			next();
		},
		INPUT : function (args, next) {
			sys.puts('> '+args[0]);
			process.stdin.resume();
			process.stdin.setEncoding('utf8');
			process.stdin.once('data', function(chunk) {
				next(chunk);
			});
			
		},
		TEST : function (args, next) {
			next(args[0] * 2);
		},
		
		/**
		 * Sets system variable lastTick to current timestamp in ms
		 * @returns {int} delay in ms between this and a previous calls (or 0 if called first)
		 */
		TICK : function (args, next) {
			var lastTick = this.sysvars.lastTick,
				now = (new Date).getTime(),
				delay = (lastTick === undefined) ? 0 : now - lastTick;
			
			this.sysvars.lastTick = now;
			next(delay);
		}
		
	};


	exports.QVM = QVM;


} ('undefined' === typeof(window) ? exports : window, undefined));
